﻿/*
    Copyright (c) 2010 OpenNETCF Consulting, LLC

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
    and associated documentation files (the "Software"), to deal in the Software without restriction, 
    including without limitation the rights to use, copy, modify, merge, publish, distribute, 
    sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all copies or 
    substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
    BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
    DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;

namespace OpenNETCF.Barcode.Decoders
{
    internal static class FixedWidthFunctions
    {
    }

    internal class EAN13Decoder : BaseDecoder
    {
        private int Resolution { get; set; }
        public EanRegion Region { get; set; }

        public EAN13Decoder()
        {
            ScanY = 50; // half way down
            Threshold = 85;
        }

        public override Barcode Decode()
        {
            var luminanceData = GetBinaryLuminanceLine(Image.Height * ScanY / 100, 0);
            var value = Decode(luminanceData);

            return new Barcode
            {
                Symbology = Symbology.EAN13,
                Value = value
            };
        }

        public override byte GetRecommendedThreshold()
        {
            // TODO:
            // the start guard pattern (1-0-1) are each one bit and should be equal width.  Maybe Look for a threshold that yields that
            return 75;
        }

        private const int BitsInBarcode = 3 + (6 * 7) + 5 + (6 * 7) + 3;  // guard, 6 digits (7 bits each) guard, 6 digits, guard

        private int GetDigitWidth(byte[] data)
        {
            // new test
            return GetDigitWidthFromBounds(data, BitsInBarcode);
        }

        public string Decode(byte[] data)
        {
            string barcode = string.Empty;

            int width = GetDigitWidth(data);
            int offset = 0;

            // move to start of data
            do
            {
                offset++;
            } while (data[offset] != byte.MaxValue);

            // ========== start guard =============
            // start guard is '101'
            int expected = 1;
            for (int i = 0; i < 3; i++)
            {
                var bit = GetBit(data, offset, width, Threshold);
                if (bit != expected) throw new DelimiterNotFoundException("start delimiter not found");

                offset += width;
                expected = (expected == 0) ? 1 : 0;
            }

            // ========== left data =============
            int mask = 0;
            int maskBit = 0;
            for (int i = 0; i < 6; i++)
            {
                var digit = GetDigit(i, data, offset, width, Threshold, out maskBit);
                barcode += digit;
                offset += width * 7;

                mask = mask | (maskBit << 5 - i);
            }

            var r = (EanRegion)mask;
            barcode = r.ToDigit() + barcode;

            // ========== middle guard =============
            // middle guard is '01010'
            expected = 0;
            for (int i = 0; i < 5; i++)
            {
                var bit = GetBit(data, offset, width, Threshold);
                if (bit != expected) throw new DelimiterNotFoundException("start delimiter not found");

                offset += width;
                expected = (expected == 0) ? 1 : 0;
            }

            // ========== right data =============
            for (int i = 6; i < 12; i++)
            {
                var digit = GetDigit(i, data, offset, width, Threshold, out maskBit);
                barcode += digit;
                offset += width * 7;
            }

            // ========== end guard =============
            // end guard is '101'
            expected = 1;
            for (int i = 0; i < 3; i++)
            {
                var bit = GetBit(data, offset, width, Threshold);
                if (bit != expected) throw new DelimiterNotFoundException("start delimiter not found");

                offset += width;
                expected = (expected == 0) ? 1 : 0;
            }
            return barcode;
        }

        private char ConvertToLeftDigit(byte data, out int mask)
        {
            mask = 0;
            switch (data)
            {
                // low mask
                case 0x0d: return '0';
                case 0x19: return '1';
                case 0x13: return '2';
                case 0x3d: return '3';
                case 0x23: return '4';
                case 0x31: return '5';
                case 0x2f: return '6';
                case 0x3b: return '7';
                case 0x37: return '8';
                case 0x0b: return '9';
            }

            mask = 1;
            switch (data)
            {
                //high mask
                case 0x27: return '0';
                case 0x33: return '1';
                case 0x1b: return '2';
                case 0x21: return '3';
                case 0x1d: return '4';
                case 0x39: return '5';
                case 0x05: return '6';
                case 0x11: return '7';
                case 0x09: return '8';
                case 0x17: return '9';
            }

            // TODO: make meaningful
            throw new Exception();
        }

        private char GetDigit(int place, byte[] data, int offset, int width, int threshold, out int mask)
        {
            char result = char.MinValue;

            if (place > 11) throw new ArgumentOutOfRangeException();

            int value = 0x00;

            // read 7 bits
            for (int i = 6; i >= 0; i--)
            {
                var bit = GetBit(data, offset, width, threshold);
                Debug.Write(bit == 0 ? "0" : "1");
                offset += width;
                value |= (bit << i);
            }

            mask = 0;

            if (place < 6)
            {
                result = ConvertToLeftDigit((byte)value, out mask);
            }
            else
            {
                switch (value)
                {
                    case 0x72: result = '0'; break;
                    case 0x66: result = '1'; break;
                    case 0x6c: result = '2'; break;
                    case 0x42: result = '3'; break;
                    case 0x5c: result = '4'; break;
                    case 0x4e: result = '5'; break;
                    case 0x50: result = '6'; break;
                    case 0x44: result = '7'; break;
                    case 0x48: result = '8'; break;
                    case 0x74: result = '9'; break;
                }
            }
            if (result == char.MinValue)
            {
                // TODO: make meaningful
                throw new Exception();
            }

            Debug.WriteLine(" = " + result.ToString());

            return result;
        }
    }
}
